﻿using System;
using System.Collections;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace LINQToolKit.Text
{
    public class TextTemplate
    {
        private const string PatternRegex = @"\(\?\<field\>[^\)]+\)";
        private string[] _patterns;
        private string _originalContent;
        private bool _emptyOnNull;

        public TextTemplate(string originalContent, string[] patterns)
            : this(originalContent, patterns, false)
        {

        }

        public TextTemplate(string originalContent, string[] patterns, bool emptyOnNull)
        {
            originalContent.AssertNull("originalContent");
            patterns.AssertNull("patterns");

            this.ValidatePattern(patterns);
            this._patterns = patterns;
            this._originalContent = originalContent;
            this._emptyOnNull = emptyOnNull;
        }

        private void ValidatePattern(string[] patterns)
        {
            foreach (var item in patterns)
            {
                if (!Regex.Match(item, PatternRegex).Success)
                {
                    throw new ArgumentException("invalid pattern", item);
                }
            }
        }

        public string Translate(object entity)
        {
            entity.AssertNull("entity");
            var result = this._originalContent;

            foreach (var item in this._patterns)
            {
                if (!string.IsNullOrEmpty(item))
                {
                    result = this.TranslatePattern(item, result, entity);
                }
            }

            return result;
        }

        private string TranslatePattern(string pattern, string givenContent, object entity)
        {
            var type = entity.GetType();
            var matches = Regex.Matches(givenContent, pattern);

            if (matches.Count == 0)
            {
                return givenContent;
            }

            for (int i = 0; i < matches.Count; i++)
            {
                var key = matches[i].Result("${field}").Trim('%');

                if (this.IsCollection(key, type))
                {
                    var sbValue = new StringBuilder();
                    this.GenerateCollection(key, matches[i], entity, sbValue);
                    givenContent = givenContent.Replace(matches[i].Value, sbValue.ToString());
                }
                else
                {
                    var value = this.GetFieldValue(key, entity);

                    if (string.IsNullOrEmpty(value) && this._emptyOnNull)
                    {
                        givenContent = givenContent.Replace(matches[i].Value, string.Empty);
                    }
                    else
                    {
                        givenContent = givenContent.Replace(matches[i].Groups[1].Value, value);
                    }
                }
            }

            return givenContent;
        }

        private bool IsCollection(string key, Type type)
        {
            var fields = key.Trim('.').Split('.');

            // loop to check is collection
            foreach (var f in fields)
            {
                var property = type.GetProperty(
                    f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                if (property == null)
                {
                    return false;
                }

                if (property.PropertyType != typeof(string) &&
                    typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
                {
                    return true;
                }
                else
                {
                    type = property.PropertyType;
                }
            }

            return false;
        }

        private string GetFieldValue(string key, object entity)
        {
            var type = entity.GetType();
            var fields = key.Trim('.').Split('.');

            foreach (var f in fields)
            {
                var property = type.GetProperty(
                    f, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                if (property == null)
                {
                    return string.Empty;
                }

                entity = property.GetValue(entity, null);

                if (entity == null)
                {
                    return string.Empty;
                }

                type = entity.GetType();
            }

            return entity.ToString();
        }

        private void GenerateCollection(
            string key, Match match, object entity, StringBuilder sbValue)
        {
            Type type = entity.GetType();
            var fields = key.Trim('.').Split('.');

            for (int i = 0; i < fields.Length; i++)
            {
                var property = type.GetProperty(
                    fields[i], BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

                if (property == null)
                {
                    return;
                }

                // the last loop
                if (i == fields.Length - 1)
                {
                    var value = match.Value.Replace(
                        match.Groups[1].Value, property.GetValue(entity, null).ToString());
                    sbValue.Append(value);
                    return;
                }
                else
                {
                    // start recursion
                    var newKey = string.Join(".", fields, i + 1, fields.Length - (i + 1));
                    var newEntity = property.GetValue(entity, null);

                    if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
                    {
                        IEnumerable collection = (IEnumerable)newEntity;

                        foreach (var item in collection)
                        {
                            this.GenerateCollection(newKey, match, item, sbValue);
                        }
                    }
                    else
                    {
                        this.GenerateCollection(newKey, match, newEntity, sbValue);
                    }
                }
            }
        }
    }
}
